/*
 * CCVisu is a tool for visual graph clustering
 * and general force-directed graph layout.
 * This file is part of CCVisu.
 *
 * Copyright (C) 2005-2011  Dirk Beyer
 *
 * CCVisu is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * CCVisu is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with CCVisu; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Please find the GNU Lesser General Public License in file
 * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt
 *
 * Dirk Beyer    (firstname.lastname@uni-passau.de)
 * University of Passau, Bavaria, Germany
 */
package org.sosy_lab.ccvisu.writers;

import java.awt.Color;
import java.io.PrintWriter;
import java.util.List;

import org.sosy_lab.ccvisu.CCVisuController;
import org.sosy_lab.ccvisu.CCVisuOptions;
import org.sosy_lab.ccvisu.InformationProvider;
import org.sosy_lab.ccvisu.graph.GraphData;
import org.sosy_lab.ccvisu.graph.GraphEdge;
import org.sosy_lab.ccvisu.graph.GraphVertex;
import org.sosy_lab.ccvisu.graph.Position;
import org.sosy_lab.common.configuration.Configuration;
import org.sosy_lab.common.configuration.InvalidConfigurationException;

/**
 * Writer for layouts in VRML format.
 */
public class WriterDataLayoutVRML extends WriterDataLayout {

  private final CCVisuController controller;

  private final CCVisuOptions    options;

  public WriterDataLayoutVRML(PrintWriter out, GraphData graph, CCVisuOptions options,
      CCVisuController controller, Configuration config)
          throws InvalidConfigurationException {

    super(out, graph, config);
    assert(controller != null && options != null);

    this.controller = controller;
    this.options = options;
  }

  /**
   * Writes the layout in graphics format VRML.
   */
  @Override
  public void write() {
    int size = (int) (1000 * options.getScalePos());

    // Header
    out.println("#VRML V2.0 utf8");
    out.println("#");
    out.println("# Generated by " + InformationProvider.toolDescription());
    out.println("#   " + InformationProvider.currentDateTime());
    out.println();
    out.println(" PROTO TRANSFORM_Sphere [");
    out.println("    field SFVec3f translation 0 0 0");
    out.println("    field SFVec3f scale 1 1 1");
    out.println("    field SFColor diffuseColor 0.5 0.5 0.5");
    out.println("    field SFString name \"\"");
    out.println("    field SFVec3f textTranslate 0 0 1");
    out.println("    field SFFloat textSize " + controller.getFontSize());
    out.println(" ] {");
    out.println("    Transform {");
    out.println("        translation IS translation");
    out.println("        children [");
    out.println("            Transform {");
    out.println("                scale IS scale");
    out.println("                children [");
    out.println("                    DEF sensor0 TouchSensor {}");
    out.println("                    Shape {");
    out.println("                        geometry Sphere { radius 0.5 }");
    out.println("                        appearance Appearance {");
    out.println("                            material Material {");
    out.println("                                ambientIntensity 0.3");
    out.println("                                diffuseColor IS diffuseColor");
    out.println("                            }");
    out.println("                        }");
    out.println("                    }");
    out.println("                ]");
    out.println("            }");
    out.println("            Billboard {");
    out.println("                axisOfRotation 0 0 0");
    out.println("                children [");
    out.println("                    Transform {");
    out.println("                        translation IS textTranslate");
    out.println("                        children [");
    out.println("                            Shape {");
    out.println("                                geometry DEF label0 Text { string [ \"\" ] fontStyle FontStyle { family \"SANS\" size IS textSize } }");
    out.println("                                appearance Appearance {");
    out.println("                                    material Material {");
    out.println("                                        ambientIntensity 0.3");
    out.println("                                        diffuseColor 0 0 0");
    out.println("                                    }");
    out.println("                                }");
    out.println("                            }");
    out.println("                        ]");
    out.println("                    }");
    out.println("                ]");
    out.println("            }");
    out.println("            DEF script0 Script {");
    out.println("                eventIn SFBool isOver");
    out.println("                eventIn SFBool touchTime");
    out.println("                eventOut MFString trans");
    out.println("                field SFBool labelDisplayed FALSE");
    out.println("                field SFString label IS name");
    out.println("                url [ \"javascript:");
    out.println("                    function isOver(value) {");
    out.println("                        if (value) {");
    out.println("                            trans[0] = label;");
    out.println("                        } else {");
    out.println("                            if (!labelDisplayed) trans[0] = '';");
    out.println("                        }");
    out.println("                    }");
    out.println("                    function touchTime() {");
    out.println("                        if (labelDisplayed) {");
    out.println("                            trans[0] = '';");
    out.println("                            labelDisplayed = false;");
    out.println("                        } else {");
    out.println("                            trans[0] = label;");
    out.println("                            labelDisplayed = true;");

    if (openUrl) {
      out.println("                            Browser.loadURL(new MFString(label), new MFString('target=DUMMY'));");
    }

    out.println("                        }");
    out.println("                    }\"");
    out.println("                ]");
    out.println("            }");
    out.println("        ]");
    out.println("    }");
    out.println("    ROUTE sensor0.isOver TO script0.isOver");
    out.println("    ROUTE sensor0.touchTime TO script0.touchTime");
    out.println("    ROUTE script0.trans TO label0.set_string");
    out.println(" }");
    out.println();

    out.println(" PROTO TRANSFORM_Cylinder [");
    out.println("    field SFVec3f translation 0 0 0");
    out.println("    field SFRotation rotation 0 0 0 0");
    out.println("    field SFFloat height 1");
    out.println("    field SFColor diffuseColor 0.5 0.5 0.5");
    out.println("    field SFString name \"\"");
    out.println("    field SFVec3f textTranslate 0 0 1");
    out.println("    field SFFloat textSize " + controller.getFontSize());
    out.println(" ] {");
    out.println("     Transform {");
    out.println("         translation IS translation");
    out.println("         children [");
    out.println("         Transform {");
    out.println("             rotation IS rotation");
    out.println("             children [");
    out.println("                 DEF sensor0 TouchSensor {}");
    out.println("                 Shape {");
    out.println("                     geometry Cylinder {");
    out.println("                         height IS height");
    out.println("                         radius 0.5");
    out.println("                         top FALSE");
    out.println("                         side TRUE");
    out.println("                         bottom FALSE");
    out.println("                     }");
    out.println("                         appearance Appearance {");
    out.println("                         material Material {");
    out.println("                             ambientIntensity 0.3");
    out.println("                             diffuseColor IS diffuseColor");
    out.println("                         }");
    out.println("                     }");
    out.println("                 }");
    out.println("             ]");
    out.println("         }");
    out.println("         Billboard {");
    out.println("            axisOfRotation 0 0 0");
    out.println("            children [");
    out.println("                Transform {");
    out.println("                    translation IS textTranslate");
    out.println("                    children [");
    out.println("                        Shape {");
    out.println("                            geometry DEF label0 Text { string [ \"\" ] fontStyle FontStyle { family \"SANS\" size IS textSize } }");
    out.println("                            appearance Appearance {");
    out.println("                                material Material {");
    out.println("                                    ambientIntensity 0.3");
    out.println("                                    diffuseColor 0 0 0");
    out.println("                                }");
    out.println("                            }");
    out.println("                        }");
    out.println("                    ]");
    out.println("                }");
    out.println("            ]");
    out.println("         }");
    out.println("         DEF script0 Script {");
    out.println("             eventIn SFBool isOver");
    out.println("             eventIn SFBool touchTime");
    out.println("             eventOut MFString trans");
    out.println("             field SFBool labelDisplayed FALSE");
    out.println("             field SFString label IS name");
    out.println("             url [ \"javascript:");
    out.println("                 function isOver(value) {");
    out.println("                     if (value) {");
    out.println("                         trans[0] = label;");
    out.println("                     } else {");
    out.println("                         if (!labelDisplayed) trans[0] = '';");
    out.println("                     }");
    out.println("                 }");
    out.println("                 function touchTime() {");
    out.println("                     if (labelDisplayed) {");
    out.println("                         trans[0] = '';");
    out.println("                         labelDisplayed = false;");
    out.println("                     } else {");
    out.println("                         trans[0] = label;");
    out.println("                         labelDisplayed = true;");
    out.println("                     }");
    out.println("                 }\"");
    out.println("             ]");
    out.println("          }");
    out.println("       ]");
    out.println("    }");
    out.println("    ROUTE sensor0.isOver TO script0.isOver");
    out.println("    ROUTE sensor0.touchTime TO script0.touchTime");
    out.println("    ROUTE script0.trans TO label0.set_string");
    out.println(" }");
    out.println();
    out.println("Background { skyColor .8 .8 .8 }");

    // Body
    writeGraphicsLayout(graph.getVertices(), graph.getEdges(), size);
  }

  /**
   * Write graphics layout.
   * @param size  Size of output area (e.g., number of pixel).
   */
  @Override
  public void writeGraphicsLayout(List<GraphVertex> vertices,
      List<GraphEdge> edges, int size) {

    float xPosMin =  1000000;
    float xPosMax = -1000000;
    float yPosMin =  1000000;
    float yPosMax = -1000000;
    float zPosMin =  1000000;
    float zPosMax = -1000000;

    for (GraphVertex vertex : graph.getVertices()) {
      if (vertex.isShowVertex()) {
        Position position = vertex.getPosition();
        xPosMin = Math.min(xPosMin, position.getX());
        xPosMax = Math.max(xPosMax, position.getX());
        yPosMin = Math.min(yPosMin, position.getY());
        yPosMax = Math.max(yPosMax, position.getY());
        zPosMin = Math.min(zPosMin, position.getZ());
        zPosMax = Math.max(zPosMax, position.getZ());
      }
    }

    float layoutDist;
    layoutDist = Math.max(xPosMax - xPosMin, yPosMax - yPosMin);
    layoutDist = Math.max(layoutDist, zPosMax - zPosMin);

    float xOffset = -xPosMin + 0.05f * layoutDist;
    float yOffset = -yPosMin + 0.05f * layoutDist;
    float zOffset = -zPosMin + 0.05f * layoutDist;

    float scale = 0.9f * size / layoutDist;

    int x = (int) (((xPosMin + xPosMax) / 2 + xOffset) * scale);
    int y = (int) (((yPosMin + yPosMax) / 2 + yOffset) * -scale + size);
    int z = (int) ((zPosMax
        + Math.max((xPosMax - xPosMin), (yPosMax - yPosMin)) * 1.3
        + zOffset) * scale);

    out.println("Viewpoint { ");
    out.println("    position  " + x + " " + y + " " + z);
    out.println("    orientation  0  0  1  0 ");
    out.println("    description \"origin\" ");
    out.println("} ");
    out.println();

    // Draw the edges.
    if (showEdges && !graph.getEdges().isEmpty()) {
      for (GraphEdge edge : graph.getEdges()) {
        Position sourcePos = edge.getSource().getPosition();
        Position targetPos = edge.getTarget().getPosition();
        writeEdge(edge,
            (int) ((sourcePos.getX() + xOffset) * scale),
            (int) ((sourcePos.getY() + yOffset) * -scale + size),
            (int) ((sourcePos.getZ() + zOffset) * scale),
            (int) ((targetPos.getX() + xOffset) * scale),
            (int) ((targetPos.getY() + yOffset) * -scale + size),
            (int) ((targetPos.getZ() + zOffset) * scale));
      }
    }

    // Draw the vertices.
    for (GraphVertex vertex : graph.getVertices()) {
      if (!vertex.isShowName()) {
        writeVertexPrepare(vertex, size, scale, xOffset, yOffset, zOffset);
      }
    }

    // Draw the annotated vertices.
    for (GraphVertex vertex : graph.getVertices()) {
      if (vertex.isShowName()) {
        writeVertexPrepare(vertex, size, scale, xOffset, yOffset, zOffset);
      }
    }
  }

  /**
   * If the vertex is visible, the parameters for the vertex writing are getting
   * prepared and the vertex is written.
   */
  private void writeVertexPrepare(GraphVertex vertex, int size, float scale,
      float xOffset, float yOffset, float zOffset) {

    if (vertex.isShowVertex()) {
      int radius = (int) Math.max(Math.pow(vertex.getDegree(), 0.5) * minVert,
          minVert);

      Position vertexPosition = vertex.getPosition();
      int xPos = (int) ((vertexPosition.getX() + xOffset) * scale);
      int yPos = (int) ((vertexPosition.getY() + yOffset) * -scale + size);
      int zPos = (int) ((vertexPosition.getZ() + zOffset) * scale);

      // Determine color for vertex.
      Color color = determineVertexColor(vertex);

      writeVertex(vertex, xPos, yPos, zPos, radius, color);
    }
  }

  /**
   * Writes a vertex in VRML format.
   * @param vertex     The vertex object, to access vertex attributes.
   * @param xPos       x coordinate of the vertex.
   * @param yPos       y coordinate of the vertex.
   * @param zPos       z coordinate of the vertex.
   * @param width      Width of the vertex.
   * @param height     Height of the vertex.
   */
  @Override
  public void writeVertex(GraphVertex vertex, int xPos, int yPos, int zPos,
                          int width, int height, Color color) {

    String name = vertex.getName();

    //remove double quote
    if ((name.length() > 0 && name.charAt(0) == '"') && name.endsWith("\"")) {
      name = name.substring(1, name.length() - 1);
    }

    int widthTwice = 2 * width;

    out.println("TRANSFORM_Sphere { translation "
        + xPos + " " + yPos + " " + zPos + " "
        + "scale "
        + widthTwice + " " + widthTwice + " " + widthTwice + " "
        + "diffuseColor "
        + color.getRed() + " " + color.getGreen() + " " + color.getBlue() + " "
        + "name \"" + name + "\" textTranslate "
        + widthTwice + " "
        + -controller.getFontSize() / 2
        + " " + widthTwice + " } ");
  }

  /**
   * Writes an edge.
   * @param edge        an edge in graph.edges
   * @param xPos1       x coordinate of the first point.
   * @param yPos1       y coordinate of the first point.
   * @param zPos1       z coordinate of the first point.
   * @param xPos2       x coordinate of the second point.
   * @param yPos2       y coordinate of the second point.
   * @param zPos2       z coordinate of the second point.
   */
  @Override
  public void writeEdge(GraphEdge edge, int xPos1, int yPos1, int zPos1,
                        int xPos2, int yPos2, int zPos2) {

    String edgeName = edge.getRelationName();

    int deltaX = xPos2 - xPos1;
    int deltaY = yPos2 - yPos1;
    int deltaZ = zPos2 - zPos1;

    float dist = (float) Math.sqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);

    // It does not make sense to draw an edge of length zero.
    if (dist == 0) {
      return;
    }

    /*
    float rotx = dy;
    float roty = -dx;
    float rotz = 0.0f;
     */
    float rotx = deltaZ;
    float roty = 0.0f;
    float rotz = -deltaX;

    float norm = (float) Math.sqrt(rotx * rotx + roty * roty + rotz * rotz);
    float rotw = (float) Math.asin(norm / dist);

    if (deltaY < 0) {
      rotw = -rotw;
    }

    out.println("TRANSFORM_Cylinder { translation "
        + (xPos1 + xPos2) / 2 + " "
        + (yPos1 + yPos2) / 2 + " "
        + (zPos1 + zPos2) / 2 + " "
        + "rotation " + rotx + " " + roty + " " + rotz + " " + rotw + " "
        + "height " + dist + " "
        + "diffuseColor 0 0 0 "
        + "name \"" + edgeName + "\" textTranslate 0 0 "
        + controller.getFontSize()
        + " } ");
  }
}
